{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2859de40-f927-4790-b192-c5b0531058f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "if \"pyodide\" in sys.modules:\n",
    "    import piplite\n",
    "    await piplite.install('pyb2d-jupyterlite-backend>=0.4.2')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ac3e93b-3e9e-4cd7-a183-0214b0dcb513",
   "metadata": {},
   "outputs": [],
   "source": [
    "from b2d.testbed import TestbedBase\n",
    "import math\n",
    "import numpy\n",
    "import b2d\n",
    "\n",
    "class AngryShapes(TestbedBase):\n",
    "\n",
    "    name = \"AngryShapes\"\n",
    "\n",
    "    class Settings(TestbedBase.Settings):\n",
    "        substeps: int = 2\n",
    "\n",
    "    def draw_segment(self, p1, p2, color, line_width=1):\n",
    "        screen_p1 = self._point(self.world_to_screen(p1))\n",
    "        screen_p2 = self._point(self.world_to_screen(p2))\n",
    "        screen_color = self._uint8_color(color)\n",
    "        screen_line_width = self._line_width(line_width)\n",
    "\n",
    "        cv.line(self._image, screen_p1, screen_p2, screen_color, screen_line_width)\n",
    "\n",
    "    def draw_polygon(self, vertices, color, line_width=1):\n",
    "        # todo add C++ function for this\n",
    "        screen_vertices = numpy.array(\n",
    "            [self._point(self.world_to_screen(v)) for v in vertices], dtype=\"int32\"\n",
    "        )\n",
    "        screen_color = self._uint8_color(color)\n",
    "        screen_line_width = self._line_width(line_width)\n",
    "\n",
    "        cv.polylines(\n",
    "            self._image, [screen_vertices], True, screen_color, screen_line_width, 8\n",
    "        )\n",
    "\n",
    "    def draw_solid_polygon(self, vertices, color):\n",
    "        # todo add C++ function for this\n",
    "        screen_vertices = numpy.array(\n",
    "            [self._point(self.world_to_screen(v)) for v in vertices], dtype=\"int32\"\n",
    "        )\n",
    "        screen_color = self._uint8_color(color)\n",
    "\n",
    "        cv.fillPoly(self._image, [screen_vertices], screen_color, 8)\n",
    "\n",
    "    def __init__(self, settings=None):\n",
    "        super(AngryShapes, self).__init__(settings=settings)\n",
    "\n",
    "        self.targets = []\n",
    "        self.projectiles = []\n",
    "        self.marked_for_destruction = []\n",
    "        self.emitter = None\n",
    "\n",
    "        # particle system\n",
    "        pdef = b2d.particle_system_def(\n",
    "            viscous_strength=0.9,\n",
    "            spring_strength=0.0,\n",
    "            damping_strength=100.5,\n",
    "            pressure_strength=1.0,\n",
    "            color_mixing_strength=0.05,\n",
    "            density=0.1,\n",
    "        )\n",
    "\n",
    "        self.psystem = self.world.create_particle_system(pdef)\n",
    "        self.psystem.radius = 1\n",
    "        self.psystem.damping = 0.5\n",
    "\n",
    "        self.build_outer_box()\n",
    "        self.build_castle()\n",
    "        self.build_launcher()\n",
    "        self.arm_launcher()\n",
    "        self.build_explosives()\n",
    "\n",
    "    def build_outer_box(self):\n",
    "        # the outer box\n",
    "\n",
    "        shape = b2d.edge_shape([(100, 0), (600, 0)])\n",
    "        box = self.world.create_static_body(\n",
    "            position=(0, 0), fixtures=b2d.fixture_def(shape=shape, friction=1)\n",
    "        )\n",
    "\n",
    "    def build_target(self, pos):\n",
    "        t = self.world.create_dynamic_body(\n",
    "            position=pos,\n",
    "            fixtures=[\n",
    "                b2d.fixture_def(shape=b2d.circle_shape(radius=4), density=1.0),\n",
    "                b2d.fixture_def(\n",
    "                    shape=b2d.circle_shape(radius=2, pos=(3, 3)), density=1.0\n",
    "                ),\n",
    "                b2d.fixture_def(\n",
    "                    shape=b2d.circle_shape(radius=2, pos=(-3, 3)), density=1.0\n",
    "                ),\n",
    "            ],\n",
    "            linear_damping=0,\n",
    "            angular_damping=0,\n",
    "            user_data=\"target\",\n",
    "        )\n",
    "        self.targets.append(t)\n",
    "\n",
    "    def build_castle(self):\n",
    "        def build_pyramid(offset, bar_shape, n):\n",
    "            def build_brick(pos, size):\n",
    "                hsize = [s / 2 for s in size]\n",
    "                self.world.create_dynamic_body(\n",
    "                    position=(\n",
    "                        pos[0] + hsize[0] + offset[0],\n",
    "                        pos[1] + hsize[1] + offset[1],\n",
    "                    ),\n",
    "                    fixtures=b2d.fixture_def(\n",
    "                        shape=b2d.polygon_shape(box=hsize), density=8\n",
    "                    ),\n",
    "                    user_data=\"brick\",\n",
    "                )\n",
    "\n",
    "            bar_length = bar_shape[0]\n",
    "            bar_width = bar_shape[1]\n",
    "\n",
    "            nxm = n\n",
    "            for y in range(nxm):\n",
    "                py = y * (bar_length + bar_width)\n",
    "                nx = nxm - y\n",
    "                for x in range(nx):\n",
    "                    px = x * bar_length + y * (bar_length) / 2.0\n",
    "                    if y + 1 < nxm - 1:\n",
    "                        if x == 0:\n",
    "                            px += bar_width / 2\n",
    "                        if x + 1 == nx:\n",
    "                            px -= bar_width / 2\n",
    "\n",
    "                    build_brick((px, py), (bar_width, bar_length))\n",
    "                    if x < nx - 1:\n",
    "                        self.build_target(\n",
    "                            pos=(\n",
    "                                px + offset[0] + bar_length / 2,\n",
    "                                py + offset[1] + bar_width,\n",
    "                            )\n",
    "                        )\n",
    "                        build_brick(\n",
    "                            (px + bar_width / 2, py + bar_length),\n",
    "                            (bar_length, bar_width),\n",
    "                        )\n",
    "\n",
    "        build_pyramid(offset=(100, 0), bar_shape=[40, 4], n=4)\n",
    "        build_pyramid(offset=(400, 0), bar_shape=[30, 3], n=4)\n",
    "\n",
    "    def build_launcher(self):\n",
    "\n",
    "        self.launcher_anchor_pos = (30, 0)\n",
    "        self.launcher_anchor = self.world.create_static_body(\n",
    "            position=self.launcher_anchor_pos\n",
    "        )\n",
    "\n",
    "    def arm_launcher(self):\n",
    "        self.reload_time = None\n",
    "        self.is_armed = True\n",
    "        self.projectile_radius = 3\n",
    "        projectile_pos = (self.launcher_anchor_pos[0], self.launcher_anchor_pos[1] / 2)\n",
    "\n",
    "        self.projectile = self.world.create_dynamic_body(\n",
    "            position=projectile_pos,\n",
    "            fixtures=b2d.fixture_def(\n",
    "                shape=b2d.circle_shape(radius=self.projectile_radius), density=100.0\n",
    "            ),\n",
    "            linear_damping=0,\n",
    "            angular_damping=0,\n",
    "            user_data=\"projectile\",\n",
    "        )\n",
    "        self.projectiles.append(self.projectile)\n",
    "        self.projectile_joint = self.world.create_distance_joint(\n",
    "            self.launcher_anchor, self.projectile, length=1, stiffness=10000\n",
    "        )\n",
    "        self.mouse_joint = None\n",
    "\n",
    "    def build_explosives(self):\n",
    "        self.explosives = []\n",
    "\n",
    "    def on_mouse_down(self, p):\n",
    "        if self.is_armed:\n",
    "            body = self.world.find_body(pos=p)\n",
    "            if body is not None and body.user_data is not None:\n",
    "                print(\"got body\")\n",
    "                if body.user_data == \"projectile\":\n",
    "                    print(\"got projectile\")\n",
    "                    kwargs = dict(\n",
    "                        body_a=self.groundbody,\n",
    "                        body_b=body,\n",
    "                        target=p,\n",
    "                        max_force=50000.0 * body.mass,\n",
    "                        stiffness=10000.0,\n",
    "                    )\n",
    "\n",
    "                    self.mouse_joint = self.world.create_mouse_joint(**kwargs)\n",
    "                    body.awake = True\n",
    "                    return True\n",
    "\n",
    "        return False\n",
    "\n",
    "    def on_mouse_move(self, p):\n",
    "        if self.is_armed:\n",
    "            if self.mouse_joint is not None:\n",
    "                self.mouse_joint.target = p\n",
    "                return True\n",
    "        return False\n",
    "\n",
    "    def on_mouse_up(self, p):\n",
    "        if self.is_armed:\n",
    "            if self.mouse_joint is not None:\n",
    "                self.world.destroy_joint(self.mouse_joint)\n",
    "                if self.projectile_joint is not None:\n",
    "                    self.world.destroy_joint(self.projectile_joint)\n",
    "                self.projectile_joint = None\n",
    "                self.mouse_joint = None\n",
    "                delta = self.launcher_anchor.position - b2d.vec2(p)\n",
    "                scaled_delta = delta * 50000.0\n",
    "                print(scaled_delta)\n",
    "\n",
    "                self.projectile.apply_linear_impulse_to_center(scaled_delta, True)\n",
    "                self.reload_time = self.elapsed_time + 1.0\n",
    "                self.is_armed = False\n",
    "        return False\n",
    "\n",
    "    def begin_contact(self, contact):\n",
    "        body_a = contact.body_a\n",
    "        body_b = contact.body_b\n",
    "        ud_a = body_a.user_data\n",
    "        ud_b = body_b.user_data\n",
    "        if ud_b == \"projectile\":\n",
    "            body_a, body_b = body_b, body_a\n",
    "            ud_a, ud_b = ud_b, ud_a\n",
    "        if ud_a == \"projectile\":\n",
    "\n",
    "            if ud_b == \"target\" or ud_b == \"brick\":\n",
    "                self.marked_for_destruction.append(body_a)\n",
    "                emitter_def = b2d.RandomizedRadialEmitterDef()\n",
    "                emitter_def.emite_rate = 20000\n",
    "                emitter_def.lifetime = 0.7\n",
    "                emitter_def.enabled = True\n",
    "                emitter_def.inner_radius = 0.0\n",
    "                emitter_def.outer_radius = 1.0\n",
    "                emitter_def.velocity_magnitude = 1000.0\n",
    "                emitter_def.start_angle = 0\n",
    "                emitter_def.stop_angle = math.pi\n",
    "                emitter_def.transform = b2d.Transform(body_a.position, b2d.Rot(0))\n",
    "                self.emitter = b2d.RandomizedRadialEmitter(self.psystem, emitter_def)\n",
    "                self.emitter_die_time = self.elapsed_time + 0.02\n",
    "\n",
    "    def pre_step(self, dt):\n",
    "\n",
    "        if self.reload_time is not None:\n",
    "            if self.elapsed_time >= self.reload_time:\n",
    "                self.arm_launcher()\n",
    "\n",
    "        # delete contact bodies\n",
    "        for body in self.marked_for_destruction:\n",
    "            if body in self.projectiles:\n",
    "                self.projectiles.remove(body)\n",
    "                self.world.destroy_body(body)\n",
    "            if body == self.projectile:\n",
    "                self.reload_time = self.elapsed_time + 1.0\n",
    "            self.marked_for_destruction = []\n",
    "\n",
    "        # delete bodies which have fallen down\n",
    "        for body in self.world.bodies:\n",
    "            if body.position.y < -100:\n",
    "                if body.user_data == \"projectile\":\n",
    "                    self.projectiles.remove(body)\n",
    "                if body.user_data == \"target\":\n",
    "                    self.targets.remove(body)\n",
    "                self.world.destroy_body(body)\n",
    "\n",
    "        # emmiter\n",
    "        if self.emitter is not None:\n",
    "            self.emitter.step(dt)\n",
    "            if self.elapsed_time >= self.emitter_die_time:\n",
    "                self.emitter = None\n",
    "\n",
    "    def draw_target(self, target):\n",
    "        center = target.position\n",
    "        center_l = target.get_world_point((-3, 3))\n",
    "        center_r = target.get_world_point((3, 3))\n",
    "        eye_left = target.get_world_point((-1, 1))\n",
    "        eye_right = target.get_world_point((1, 1))\n",
    "        pink = [c / 255 for c in (248, 24, 148)]\n",
    "\n",
    "        self.debug_draw.draw_solid_circle(\n",
    "            center=center, radius=4, axis=None, color=pink\n",
    "        )\n",
    "        self.debug_draw.draw_solid_circle(\n",
    "            center=center_l, radius=2, axis=None, color=pink\n",
    "        )\n",
    "        self.debug_draw.draw_solid_circle(\n",
    "            center=center_r, radius=2, axis=None, color=pink\n",
    "        )\n",
    "\n",
    "        # schnautze\n",
    "        nose_center = target.get_world_point((0, -1))\n",
    "        nose_center_l = target.get_world_point((-0.3, -1))\n",
    "        nose_center_r = target.get_world_point((0.3, -1))\n",
    "\n",
    "        self.debug_draw.draw_circle(\n",
    "            center=nose_center,\n",
    "            radius=2,\n",
    "            # axis=None,\n",
    "            color=(1, 1, 1),\n",
    "            line_width=0.2,\n",
    "        )\n",
    "        # eyes\n",
    "        for nose_center in [nose_center_l, nose_center_r]:\n",
    "            self.debug_draw.draw_solid_circle(\n",
    "                center=nose_center, radius=0.6, axis=None, color=(1, 1, 1)\n",
    "            )\n",
    "        # eyes\n",
    "        for eye_center in [eye_left, eye_right]:\n",
    "            self.debug_draw.draw_solid_circle(\n",
    "                center=eye_center, radius=1, axis=None, color=(1, 1, 1)\n",
    "            )\n",
    "            self.debug_draw.draw_solid_circle(\n",
    "                center=eye_center, radius=0.7, axis=None, color=(0, 0, 0)\n",
    "            )\n",
    "\n",
    "    def draw_projectile(self, projectile):\n",
    "\n",
    "        center = projectile.position\n",
    "        # center_l = target.get_world_point((-3,3))\n",
    "        # center_r = target.get_world_point(( 3,3))\n",
    "        eye_left = projectile.get_world_point((-1, 1))\n",
    "        eye_right = projectile.get_world_point((1, 1))\n",
    "\n",
    "        self.debug_draw.draw_solid_circle(\n",
    "            center=center,\n",
    "            radius=self.projectile_radius * 1.1,\n",
    "            axis=None,\n",
    "            color=(1, 0, 0),\n",
    "        )\n",
    "\n",
    "        # eyes\n",
    "        for eye_center in [eye_left, eye_right]:\n",
    "            self.debug_draw.draw_solid_circle(\n",
    "                center=eye_center, radius=1, axis=None, color=(1, 1, 1)\n",
    "            )\n",
    "            self.debug_draw.draw_solid_circle(\n",
    "                center=eye_center, radius=0.7, axis=None, color=(0, 0, 0)\n",
    "            )\n",
    "\n",
    "    def post_debug_draw(self):\n",
    "        for target in self.targets:\n",
    "            self.draw_target(target)\n",
    "\n",
    "        for projectile in self.projectiles:\n",
    "            self.draw_projectile(projectile)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6df7c8b9-216b-4fd2-8ee8-aeec294e149d",
   "metadata": {},
   "source": [
    "# Controlls\n",
    "* To play this game, click and drag the red ball and release it to shot it.\n",
    "* Use the mouse-wheel to zoom in/out, a\n",
    "* Click and drag in the empty space to translate the view."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "df412e76-7a9a-4e1d-8bc7-c02e222e10dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pyb2d_jupyterlite_backend.async_jupyter_gui import JupyterAsyncGui\n",
    "s = JupyterAsyncGui.Settings()\n",
    "s.resolution = [1000,500]\n",
    "s.scale = 2\n",
    "s.translate = [100,100]\n",
    "tb = b2d.testbed.run(AngryShapes, backend=JupyterAsyncGui, gui_settings=s);"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
